package jmine.tec.report.impl.table;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jmine.tec.report.api.RendererParameters;
import jmine.tec.report.api.table.ReportCell;
import jmine.tec.report.api.table.ReportColumn;
import jmine.tec.report.api.table.ReportRow;
import jmine.tec.report.api.table.ReportTable;
import jmine.tec.report.api.table.ReportTableGroup;
import jmine.tec.report.api.table.ReportTableGroupCell;
import jmine.tec.report.impl.RendererParametersImpl;

import org.apache.commons.lang.builder.EqualsBuilder;

/**
 * Implementação padrão de grupo de elementos de relatório.
 * 
 * @param <V> tipo de registro
 * @author lundberg
 */
public class ReportTableGroupImpl<V> implements ReportTableGroup<V>, Serializable {

    private final ReportTable<V> table;

    private final Map<CellValueResolver<V>, Object> groupColumns;

    private final Map<ReportColumn<V>, ReportTableGroupCell<V>> groupHeader;

    private final Map<ReportColumn<V>, ReportTableGroupCell<V>> groupFooter;

    private RendererParameters parameters;

    /**
     * Construtor
     * 
     * @param table table
     */
    public ReportTableGroupImpl(ReportTable<V> table) {
        this.table = table;
        this.groupColumns = new HashMap<CellValueResolver<V>, Object>();
        this.groupHeader = new HashMap<ReportColumn<V>, ReportTableGroupCell<V>>();
        this.groupFooter = new HashMap<ReportColumn<V>, ReportTableGroupCell<V>>();
        this.parameters = new RendererParametersImpl();
    }

    /**
     * Adiciona uma coluna cuja mudança de valor deve gerar uma mudança de grupo.
     * 
     * @param resolver resolver
     */
    public void addColumn(CellValueResolver<V> resolver) {
        this.groupColumns.put(resolver, null);
    }

    /**
     * {@inheritDoc}
     */
    public void addFooterCell(ReportColumn<V> column, ReportTableGroupCell<V> groupCell) {
        this.groupFooter.put(column, groupCell);
    }

    /**
     * {@inheritDoc}
     */
    public void addHeaderCell(ReportColumn<V> column, ReportTableGroupCell<V> groupCell) {
        this.groupHeader.put(column, groupCell);
    }

    /**
     * {@inheritDoc}
     */
    public boolean isGroupChange(V nextRecord) {
        for (Map.Entry<CellValueResolver<V>, Object> entry : this.groupColumns.entrySet()) {
            if (!this.isEquals(entry.getValue(), entry.getKey().resolveCellValue(nextRecord))) {
                return true;
            }
        }
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public List<ReportRow> onGroupEnd() {
        if (this.groupFooter.isEmpty()) {
            return Collections.emptyList();
        }
        Map<String, ReportCell> cellMap = new HashMap<String, ReportCell>();
        for (ReportColumn<V> column : this.table.getColumns()) {
            cellMap.put(column.getId(), this.createCell(this.groupFooter, column));
        }
        ReportRow row = new ReportRowImpl<V>(cellMap);
        row.getParameters().setParent(this.parameters);
        return Collections.singletonList(row);
    }

    /**
     * {@inheritDoc}
     */
    public List<ReportRow> onGroupStart() {
        if (this.groupHeader.isEmpty()) {
            return Collections.emptyList();
        }
        Map<String, ReportCell> cellMap = new HashMap<String, ReportCell>();
        for (ReportColumn<V> column : this.table.getColumns()) {
            cellMap.put(column.getId(), this.createCell(this.groupHeader, column));
        }
        ReportRow row = new ReportRowImpl<V>(cellMap);
        row.getParameters().setParent(this.parameters);
        return Collections.singletonList(row);
    }

    /**
     * {@inheritDoc}
     */
    public void processRecord(V record) {
        for (Map.Entry<CellValueResolver<V>, Object> entry : this.groupColumns.entrySet()) {
            entry.setValue(entry.getKey().resolveCellValue(record));
        }
        for (ReportTableGroupCell<V> cell : this.groupHeader.values()) {
            cell.processRecord(record);
        }
        for (ReportTableGroupCell<V> cell : this.groupFooter.values()) {
            cell.processRecord(record);
        }
    }

    /**
     * Verifica se os dois objetos são iguais.
     * 
     * @param o1 o1
     * @param o2 o2
     * @return boolean
     */
    private boolean isEquals(Object o1, Object o2) {
        return new EqualsBuilder().append(o1, o2).isEquals();
    }

    /**
     * Cria a célula referente ao tableGroupCell
     * 
     * @param groupCells groupCells
     * @param column column
     * @return ReportCell
     */

    private ReportCell createCell(Map<ReportColumn<V>, ReportTableGroupCell<V>> groupCells, ReportColumn<V> column) {
        if (groupCells.containsKey(column)) {
            ReportTableGroupCell<V> groupCell = groupCells.get(column);
            return this.prepareCell(groupCell.createCell());
        } else {
            return this.prepareCell(new ReportCellImpl(null));
        }
    }

    /**
     * Clona a célula dada e aplica os parâmetros de renderer necessários
     * 
     * @param cell cell
     * @return ReportCell
     */
    private ReportCell prepareCell(ReportCell cell) {
        ReportCell copy = cell.copy(cell.getValue());
        copy.getParameters().setParent(this.parameters);
        return copy;
    }

    /**
     * {@inheritDoc}
     */
    public RendererParameters getParameters() {
        return this.parameters;
    }
}
